ID 令牌和访问令牌:有什么区别?

困惑的开发人员

ID 令牌和访问令牌:有什么区别?

了解什么是 ID 令牌和访问令牌,以及如何在 OpenID Connect 和 OAuth 上下文中正确使用它

最后更新日期:2021年10月28日

https://auth0.com/blog/id-token-access-token-what-is-the-difference/

“让我们使用令牌来保护此 API 调用。我应该使用 ID 令牌还是访问令牌?🤔 ID 令牌对我来说看起来更好。毕竟,如果我知道用户是谁,我可以做出更好的授权决策,对吧?”

你有没有发现自己提出了类似的论点?基于直觉的选择可能听起来不错,但看起来直观的东西并不总是正确的。对于 ID 和访问令牌,它们具有明确且定义明确的用途,因此应基于此使用它们。使用错误的令牌可能会导致解决方案不安全。

“到底发生了什么变化?它们只是令牌。我可以根据需要使用它们。可能发生的最坏情况是什么?

让我们仔细看看这两种类型的令牌,以更好地了解它们在身份验证和授权过程中的作用。

如果您愿意,还可以观看有关同一主题的此视频:

什么是 ID 令牌?

ID 令牌是证明用户已通过身份验证的项目。它是由OpenID Connect(OIDC)引入的,OpenID Connect是许多身份提供商(如Google,Facebook,当然还有Auth0)使用的身份验证开放标准。查看本文档,了解有关 OpenID Connect 的更多详细信息。让我们快速看一下 OIDC 想要解决的问题。

请考虑下图:

图片

在这里,用户使用其浏览器对OpenID提供程序进行身份验证,并访问Web应用程序。基于 OpenID Connect 的身份验证过程的结果是 ID 令牌,该令牌作为用户已通过身份验证的证明传递给应用程序。

这提供了 ID 令牌是什么的一个非常基本的概念:用户身份验证的证明。让我们看看其他一些细节。

ID 令牌被编码为 JSON Web 令牌 (JWT),这是一种标准格式,允许应用程序轻松检查其内容,并确保它来自预期的颁发者,并且没有其他人对其进行更改。如果您想了解有关 JWT 的更多信息,请查看 JWT 手册

简单地说,ID令牌的示例如下所示:

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwOi8vbXktZG9tYWluLmF1dGgwLmNvbSIsInN1YiI6ImF1dGgwfDEyMzQ1NiIsImF1ZCI6IjEyMzRhYmNkZWYiLCJleHAiOjEzMTEyODE5NzAsImlhdCI6MTMxMTI4MDk3MCwibmFtZSI6IkphbmUgRG9lIiwiZ2l2ZW5fbmFtZSI6IkphbmUiLCJmYW1pbHlfbmFtZSI6IkRvZSJ9.bql-jxlG9B_bielkqOnjTY9Di9FillFb6IMQINXoYsw

当然,这对人类来说是不可读的,所以你必须解码它才能看到JWT包含的内容。顺便说一句,ID令牌没有加密,而只是Base 64编码。可以使用许多可用库之一对其进行解码,也可以使用 jwt.io 调试器自行检查它。
在不深入细节的情况下,上述 ID 令牌携带的相关信息如下所示:

1
2
3
4
5
6
7
8
9
10
{ 
"iss": "http://my-domain.auth0.com",
"sub": "auth0|123456",
"aud": "1234abcdef",
"exp": 1311281970,
"iat": 1311280970,
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe"
}

这些 JSON 属性称为声明,它们是关于用户和令牌本身的声明。有关用户的声明定义了用户的标识。

实际上,OpenID Connect 规范不需要 ID 令牌来声明用户。在其最小的结构中,它没有关于用户的数据;只是有关身份验证操作的信息。
一个重要的主张是索赔。此声明定义令牌的受众,即作为令牌最终接收者的 Web 应用程序。对于 ID 令牌,其值是应使用该令牌的应用程序的客户端 ID。aud

请记住有关受众声明的这个小细节,因为它将帮助您更好地了解其正确的用法。
ID 令牌可能包含有关用户的其他信息,例如其电子邮件地址、图片、生日等。

最后,也许是最重要的事情:ID令牌由颁发者使用其私钥签名。这可以保证令牌的来源,并确保它未被篡改。您可以使用颁发者的公钥来验证这些内容。

现在您知道了什么是 ID 令牌。但是,您可以使用ID令牌做什么?

首先,它证明用户已由您信任的实体(OpenID 提供程序)进行身份验证,因此您可以信任有关其身份的声明

此外,应用程序还可以通过使用 ID 令牌中包含的有关用户的声明来个性化用户体验。例如,你可以在 UI 上显示他们的姓名,或在其生日当天显示“最良好祝愿”消息。有趣的部分是,您不需要提出其他请求,因此您的应用程序的性能可能会略有提高

什么是访问令牌?

现在,你已了解 ID 令牌是什么,让我们尝试了解什么是访问令牌。

让我们首先描述访问令牌适合的方案:

图片

在上图中,客户端应用程序希望访问资源,例如,API或其他任何可以防止未经授权访问的内容。该图中的其他两个元素是用户(即资源的所有者)和授权服务器。在此方案中,访问令牌是允许客户端应用程序访问用户资源的项目。它由授权服务器在成功对用户进行身份验证并征得其同意后颁发。

OAuth 2 上下文中,访问令牌允许客户端应用程序访问特定资源以代表用户执行特定操作。这就是所谓的委派授权方案:用户委派客户端应用程序代表他们访问资源。这意味着,例如,你可以授权你的LinkedIn应用代表你访问 Twitter 的 API,以便在两个社交平台上交叉发布。请记住,你仅授权LinkedIn在 Twitter 上发布你的帖子。您不会授权它删除它们或更改个人资料的数据或执行其他操作。此限制在委派授权方案中非常重要,可通过作用域实现。作用域是一种允许用户授权第三方应用程序仅执行特定操作的机制。

当然,接收访问令牌的 API 必须确保它实际上是由它信任的授权服务器颁发的有效令牌,并根据与其关联的信息做出授权决策。换句话说,API 需要以某种方式使用该令牌,以便授权客户端应用程序对资源执行所需的操作。

应如何使用访问令牌来做出授权决策取决于许多因素:整体系统体系结构、令牌格式等。例如,访问令牌可以是允许 API 从与授权服务器共享的数据库中检索所需信息的密钥,也可以直接包含编码格式的所需信息。这意味着了解如何检索做出授权决策所需的信息是授权服务器和资源服务器(即API)之间的协议

OAuth 2 核心规范对访问令牌格式只字未提。它可以是任何格式的字符串。用于访问令牌的常见格式是 JWT,并且可以使用标准结构。但是,这并不意味着访问令牌应采用该格式。

好!现在,你已知道什么是 ID 令牌和访问令牌。🎉 因此,您已经准备好使用它们,而不必担心犯错误。但是,等等。我没有看到你被说服。🤔 也许你需要一些其他信息。还行。因此,让我们看看这些令牌不适合什么。

什么是ID令牌不适合?

开发人员使用 ID 令牌时最常犯的错误之一是使用它来调用 API。

如上所述,ID 令牌证明用户已通过身份验证。在第一方场景中,即在客户端和 API 都由您控制的场景中,您可能会认为您的 ID 令牌适合做出授权决策:也许您需要知道的只是用户身份。

但是,即使在这种情况下,由客户端和 API 组成的应用程序的安全性也可能面临风险。实际上,没有将 ID 令牌绑定到客户端 API 通道的机制。如果攻击者设法窃取了您的ID令牌,他们可以使用它来调用您的API,就像合法客户端一样。

另一方面,对于访问令牌,有一组技术(统称为发送方约束)允许您将访问令牌绑定到特定发送方。这可以保证,即使攻击者窃取了访问令牌,他们也无法使用它来访问 API,因为令牌绑定到最初请求它的客户端。

在第三方客户端想要调用您的 API 的委托授权场景中,您不得使用 ID 令牌来调用 API。除了缺乏将其绑定到客户端的机制之外,还有其他几个原因不这样做。

如果您的 API 接受 ID 令牌作为授权令牌,那么首先,您将忽略受众声明中声明的预期收件人。该声明表示它适用于客户端应用程序,而不是资源服务器(即 API)。

您可能认为这只是一种形式,但这里有安全隐患

首先,在其他验证检查中,API 不应接受不适合它的令牌。如果是这样,其安全性将面临风险。事实上,如果你的 API 不关心令牌是否适合它,则可以使用从任何客户端应用程序窃取的 ID 令牌来访问你的 API。当然,检查受众只是您的API为防止未经授权的访问而应该做的检查之一。

此外,您的ID令牌将没有授予范围(我知道,这是另一个痛点)。如前所述,作用域允许用户限制客户端应用程序可以代表他们执行的操作。这些作用域与访问令牌相关联,以便 API 知道客户端应用程序可以执行哪些操作,不能执行哪些操作。如果客户端应用程序使用 ID 令牌来调用 API,则忽略此功能,并可能允许应用程序执行用户未授权的操作。

什么是访问令牌不适合?

在访问令牌方面,它被设想为证明您有权访问资源,例如,调用API。

客户端应用程序应仅出于此原因使用它。换句话说,客户端应用程序不应检查访问令牌。它适用于资源服务器,客户端应用程序应将访问令牌视为不透明字符串,即没有特定含义的字符串。即使知道访问令牌格式,也不应尝试在客户端应用程序中解释其内容。如前所述,访问令牌格式是授权服务器和资源服务器之间的协议,客户端应用程序不应入侵。想想如果有一天访问令牌格式发生更改会发生什么情况。如果客户端代码正在检查该访问令牌,则现在它将意外中断。

快速回顾

对 ID 和访问令牌的使用感到困惑是非常普遍的,并且很难围绕这些差异进行思考。也许它主要源于没有清楚地了解OAuth和OpenID Connect规范定义的每个工件的不同目标。此外,了解这些工件最初用于操作的场景对于防止混淆其使用具有重要作用。尽管如此,我希望这个话题现在更清楚一些。

回顾一下,下面简要总结了你对 ID 和访问令牌可以做什么和不能做什么的了解:

图片

如果要查看 ID 和访问令牌的实际效果,请注册免费的 Auth0 帐户,并开始使用首选编程语言和框架在几分钟内向应用程序添加身份验证和授权。

OAuth2ImplicitGrantandSPA

The OAuth2 working group published a new general security best current practices document which recommends a new approach for using OAuth2 to invoke API from JavaScript in Single Page Applications (SPAs). Namely, it suggests to use the authorization code grant with Proof Key for Code Exchange (PKCE) to request access tokens from SPAs, as opposed to the original OAuth2 spec proposing use of the implicit grant for that scenario.

The new guidance does not stem from any newly discovered vulnerability: if you are satisfied with the threat model of your SPAs based on current guidance, you have no new reasons to update. That said, the new guidance does confer significant advantages: it is strongly recommended that you consider it, especially for brand new applications.

If you choose this new approach, the Auth0 endpoints support all the features you need to start using the new approach today.

A More Detailed Summary

The original OAuth2 specification introduces the implicit grant in SPAs as the way JavaScript code can obtain access tokens and call APIs directly from a browser. Returning access tokens in a URL (the technique used by the implicit grant for SPAs) is fraught by known systemic issues requiring explicit mitigation. However, given the state of browser and web technologies when the grant was first introduced, that was also the only game in town for the scenario.

The OAuth2 working group determined it’s time to recommend a different grant to obtain access tokens from SPAs — specifically the authorization code grant for public clients with PKCEMain drivers appear to have been the ubiquity of CORS and the emergence of sender-constrained technologies.

The new recommendation imposes more requirements on the authorization server, but it doesn’t suffer from the issues inherent in returning access tokens in a URL. That results in less mitigation logic required by the client.

How does this impact developers like yourself?

  • If you are building a new SPA, you should consider implementing the new guidance based on authorization code with PKCE. More details below.
  • If you already have SPA apps in your portfolio, they are likely based on the implicit flow — and almost certainly already take steps to mitigate the known issues the approach entails. It is up to you to decide whether you are still satisfied with the mitigations you already have in place, or if it’s worth it to update your code to adhere to the new recommendations.
    If you want to implement the new recommendations, you’ll need to verify whether the authorization server you are using in your apps support the features required to correctly implement the authorization code grant from JavaScript.

If you are using Auth0:

  • The existing Auth0 JavaScript SDKs are based on the traditional guidance. Please ensure you are implementing the mitigations that are appropriate for your scenario. More about this later in the post.
  • The Auth0 endpoints support all the features required to implement the new recommendation: you can find sample code later in this post
  • Support for this new approach is now available through our new Auth0 Single Page App SDK.
    That’s the short version! The rest of the post will greatly expand on that, helping you to understand the problems the new approach is poised to solve, what concrete options you have available today and what might be coming.

“Auth0 Principal Architect @vibronet on the implication of new OAuth2 security BCP for implicit grant and SPAs.”

Keeping things in perspective

Some of you might find that the above summary (and this post in general) doesn’t convey the urgency and the alarmed tone you might have noticed in other discussions calling for the immediate cessation of any use of the implicit flow in general. That might have given you a bit of cognitive whiplash, considering that the shortcomings of that grant have been known since 2012 and a very large number of SPAs currently in production, including very prominent products widely adopted and being used every day without major disruptions.

Often those discussions feature well-intentioned generalizations, borne out of necessity to keep communications concise and the discussion accessible to the non-initiated. Add to that the fact that, as trusted security experts, we tend to favor the better-safe-than-sorry mantra, and you’ll get advice that often lacks nuance. It’s a bit like recommending you to always go buy your groceries in a tank: we’ll feel confident we gave you advice that will keep you secure, but you are left to figure out how to exchange your Prius for a tank, how to find parking for it, etc… and if you don’t live in a war zone, perhaps the actual risk doesn’t justify the investment.

The challenge is that when it comes to standards and security, often deciding whether you live in a war zone or not comes down to reading long specs, paying attention to language with lawyer-like focus. In this post we’ll try to spare you some of that and equip you with actionable information, so that you can decide for yourself if and when to do your investment. But of course, when in doubt, if you can afford it, a tank is a pretty cool ride.

The Implicit Grant

Broad statements indicating the deprecation of the implicit grant as a whole are overgeneralizations. OAuth2 defines the implicit grant as pretty much any flow that will result in the authorization server (AS from now on) issuing a token directly from the authorization endpoint, as opposed to issuing it from the token endpoint. The issue tackled by the new guidance focuses on some specific uses of the implicit grant in the context of SPAs, namely the ones in which access tokens are returned in a URL. Other implicit grant uses, such as the ones in which tokens are sent to a server via POST and/or the ones in which the tokens returned are idtokens, are unaffected. You can merrily keep POSTing idtokens to your middle tier for the purpose of signing in with your postback based web apps, and you can keep retrieving id_tokens for your SPA via implicit grant if you are going to consume them in your JS code.

Implicit grant and SPAs

In the case of single page applications calling 3rd party APIs, the client is the JavaScript code running in the browser and the required artifact is the access token required to gain access to the API. Here’s a quick diagram for you.

图片

Figure 1: Getting an access token in a SPA via implicit grant

Following the OAuth2 protocol syntax, the client will craft a request with a response_type indicating that the response should contain an access token, such as tokentoken id_token and code token id_token (where the combinations containing id_token are common OpenId Connect cases). The client will either redirect the browser to the request URL, or will use a popup to do the same — depending on the tastes of the authors of your SDK of choice.

Here there’s a sample request for the tokencase. Please note, all the traces in this post are edited for clarity.

1
2
3
4
5
6
7
(1) GET https://flosser.auth0.com/authorize
?audience=https://flosser.com/api/
&client_id=ZuGSLz6HjGRA8LMtopHBzcKHhCXFtMk8
&response_type=token
&redirect_uri=http://localhost:3000/
&scope=openid profile email read:appointments
&state=LxfKVRpEwa4lnPEvmx9LbbXSRIVUMaju

Feel free to ignore the audience parameter there, an identifier we use in Auth0 to indicate 3rd party resources. It doesn’t change the substance of the grant here.
For all the response_type values listed earlier, the default response_mode (the mechanism through which the requested artifact will be returned) is fragment – which means that upon successful authentication and consent, the requested token will be returned directly in an HTTP # fragment. Below you can see an example of successful response.

1
2
3
4
(2) 302 HTTP/2.0
location: http://localhost:3000/#access_token=eyJ0e[..]ju
set-cookie: auth0=s%3AKn[..]Gs; Path=/; Expires=Thu, 15 Nov 2018 19:01:25 GMT; HttpOnly; Secure
Found. Redirecting to <a href="http://localhost:3000/#access_token=eyJ0e [..] ju">http://localhost:3000/#access_token=eyJ0e ju</a>

Your SDK will expect a response of that form, and will take care of extracting the token and make it available to whatever API calling logic your client contains.

Issues

Returning access tokens through the mechanism just described creates numerous opportunities for attackers, which must be explicitly addressed to reduce the attack surface of your application. Here there’s a quick list. For more details, you can refer directly to the best practices doc.

  1. Insufficient redirect URI validation. If your AS of choice doesn’t enforce a strict match between the redirect URI requested at runtime and the one previously registered for your client, for example by accepting URLs containing wildcards, an attacker might redirect the response (containing the token you requested!) to a URL they control and harvest your credentials. Note: Auth0 enforces strict matching by default.
  2. Credential leakage by referrer header. Without explicit countermeasures (like setting the Referrer-Policy header), requesting a page right after authentication could leak the access token in the Referer header to whoever controls that page .
  3. Browser history. The fragment with the access token may end up in your browser’s history, hence exposed to any attack accessing it .
  4. Token injection. Say that an attacker manages to modify a response and substitute the token coming from the AS with one stolen from elsewhere, for example a token issued for a completely different user. There’s no way to detect this happened when using the implicit grant as described.
    Apart from the last one, all of the issues listed here have mainstream mitigations. That doesn’t mean it’s always easy to apply those mitigations, nor that every vendor does so consistently, but they were enough to make the implicit flow a viable mechanism for securing SPAs across the industry for half a decade. Think of this use of the implicit grant in SPAs as a pill for the headache that you can only take with a full stomach, can’t be taken with alcohol, but when used correctly, does make the headache go away.

Wouldn’t it be great if they’d invent a pill you can just take without worrying about all those extra precautions? That’s what the new best practice promises to deliver.

Using the Authorization Code Grant from JavaScript

In a nutshell, the recommendation in the best practice doc is simply to implement SPA clients like we’ve been implementing native clients: with the authorization code grant.

The idea is that the redirect leg of the flow, the one engaging with the authorization endpoint, would only be used to request and obtain an authorization code, and that that interaction would be protected by PKCE to prevent attackers from messing with the code. Once obtained the authorization code, the JavaScript would then proceed to redeem it with the token endpoint- just like it’s done by mobile and desktop clients. That would nicely make issues I, II, and III moot.

图片

Figure 2: getting an access token in a SPA via authorization code grant

The catch? The token endpoint on your AS of choice MUST support CORS for the trick to come together. That’s not too exotic a feature, but I believe that at this time not every major vendor supports it yet.

If you want to try it yourself, you can play with the sample that our awesome Jose Romaniello put together for the occasion. You can find the repo here. Please note that the code is meant to demonstrate the grant used in the most straightforward possible way, hence you should consider it as an educational tool rather than production-ready code.

The best way to get a feeling of how different this grant is from the implicit one is to compare and contrast what actually goes on the wire.

Here there’s the initial request for an authorization code, leg 1 in the diagram.

1
2
3
4
5
6
7
8
9
GET https://flosser.auth0.com/authorize
?audience=https://flosser.com/api/
&redirect_uri=http://localhost:3000/
&client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
&response_type=code
&scope=openid profile email read:appointments
&code_challenge=xc3uY4-XMuobNWXzzfEqbYx3rUYBH69_zu4EFQIJH8w
&code_challenge_method=S256
&state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI

That looks like a classic authorization code request, doesn’t it? Note the code_challenge and code_challenge_method, showing that we are using PKCE.
Assuming successful authentication and consent (not traced here), the response in leg 2 of the diagram is what you’d expect from this grant:

1
2
3
4
5
6
302 HTTP/2.0
location: http://localhost:3000/
?code=DiXOZqP_t1eX1CVc
&state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI
set-cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg; Path=/; Expires=Sun, 06 Jan 2019 19:25:57 GMT; HttpOnly; Secure
Found. Redirecting to <a href="http://localhost:3000/?code=DiXOZqP_t1eX1CVc&amp;state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI">http://localhost:3000/?code=DiXOZqP_t1eX1CVc&amp;state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI</a>

Your JavaScript code picks up the code, and redeems it against the token endpoint. This is the part where the CORS support for the token endpoint becomes necessary. In the trace below, representing leg 3 of the diagram, I purposely left in headers I normally omit for clarity… I want to make sure you have a chance to experience the cognitive dissonance to see a browser user-agent string in a code redemption message. I guess I’ll get used to it eventually, but I am still doing a double-take whenever I see it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
POST https://flosser.auth0.com/oauth/token HTTP/2.0
authority: flosser.auth0.com
content-length: 231
origin: http://localhost:3000
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
content-type: application/x-www-form-urlencoded; charset=UTF-8
accept: */*
referer: http://localhost:3000/?code=DiXOZqP_t1eX1CVc&state=p1Qaoiy67CA1J7iNRaPsu1AWQYXVShYI
audience=https%3A%2F%2Fflosser.com%2Fapi%2F
&client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2F
&grant_type=authorization_code
&code_verifier=Huag6ykQU7SaEYKtmNUeM8txt4HzEIfG
&code=DiXOZqP_t1eX1CVc

Please note the code verifier here, to close the PKCE check.
Leg 4 of the diagram is just the AS returning the requested token(s).

1
2
3
4
5
6
7
8
9
10
11
200 HTTP/2.0
date: Thu, 03 Jan 2019 19:25:58 GMT
content-type: application/json
content-length: 1932

{
"access_token":"eyJ[..]7GQ",
"id_token":"eyJ0[..]kH7g",
"expires_in":86400,
"token_type":"Bearer"
}

Ta dah! We got back an access token to our JavaScript client, without leaving any trace of it in the browser history or in future referral header, and without exposing ourselves to the risk of routing the token bits to the wrong place via redirects. It’s magic!
Wait a minute though, you might say. What about the refresh token? Why isn’t it returned along with the other tokens?

I am glad you asked. It’s… complicated.

Renewing access tokens

The approach described so far works for the initial access token acquisition, entailing explicit user interaction. Access tokens don’t strictly need to, but they often expire after a relatively short time. A best practice creates natural opportunities for enforcing revocation and minimizing exposure. When an access token expires, native apps implemented as public clients don’t usually force the end user through another prompt — they use refresh tokens to silently obtain a new access token.

Here comes the fun part. If you search through stackoverflow and blogs, you’ll see that one of the reasons why identity experts pushed back when customers proposed use of authorization code in SPA — the risk of leaking refresh tokens. Refresh tokens are powerful artifacts, and in the case of public clients you don’t even need to steal a secret to use them: once you get your hands on a refresh token, you can use it right away. The fact that a browser doesn’t really offer safe places to save critical artifacts (see all the debacle on the use of storage for access tokens, a far less powerful credential, and the significant risk of XSS attacks dumping them out) didn’t help at all, of course.

What changed? Why is it suddenly OK to use refresh tokens in a browser? The answer lies mostly in the emerging sender-constrained tokens technologies. Using token binding or mutual TLS authentication, it is possible to tie a particular refresh token to the client instance it originally requested; that means that an attacker obtaining the bits of a refresh token wouldn’t be able to use it. That’s the theory, at least: unfortunately neither technologies are widely available in the wild. Token binding suffered an important setback when Chrome walked back its support, and iOS never committed to implementation. The MTLS spec is more promising in term of potential adoption, but it’s still early days (it’s still a draft being actively worked on) and there are details to iron out (for example, using this technique from a browser might pop out UX unexpectedly). That means that in practice today you can’t use sender constrained refresh tokens with most mainstream providers.

Not everything is lost, though. The best practices doc states that another acceptable mechanism for protecting refresh tokens for use in public clients (not just browsers) is refresh token rotation, a feature that invalidates a refresh token and issues a new one whenever it is used to refresh an access token. Security-wise, that’s a great feature. In real life that can become a handful: think of what happens if you have multiple client instances sharing a token store (you need to serialize access) or if you successfully use a refresh token but fail to receive a new one. Either way: I personally know of only two products supporting refresh token rotation as of today. Neither Microsoft, Google, nor Auth0 offer it at the moment. *[update: since then, Auth0 added support for refresh token rotation! please see*https://auth0.com/docs/libraries/auth0-spa-js?ga=2.198231919.1941454603.1594245172-762247280.1581520636#use-rotating-refresh-tokens for details]_

Given that the guidance above is about public clients, not just browsers, it would appear that most current native clients violate it (though that doesn’t make them non-compliant with the standard, given that the best practices document doesn’t amend the original OAuth2 specification).

What to do? Considering that a browser is a far more dangerous environment than, say, a mobile platform app sandbox, I would recommend that unless (or until) your scenario does offer one of those refresh token protection features, that you do NOT use refresh tokens in your SPAs.

Luckily, there is an alternative to renew access tokens in SPAs without using refresh tokens AND without falling back to implicit. You can leverage the presence of a session cookie with the AS to request a new authorization code without showing any UX via hidden iframe and prompt=none (assuming your AS does OpenId Connect as well). That is the same trick we use in the implicit flow, but the difference is that here we get back a code rather than a token — obtaining the same advantages we did in the interactive version of the flow.

That’s what we demonstrate in Jose’s sample code. In the trace below you can see a request for a new access token via hidden iframe.

1
2
3
4
5
6
7
8
9
10
11
12
GET https://flosser.auth0.com/authorize
?audience=https://flosser.com/api/
&redirect_uri=http://localhost:3000/
&client_id=IrbblpeZJRMewq8suTx0PcmHlporj6yZ
&response_type=code
&response_mode=web_message
&prompt=none
&scope=openid profile email read:appointments
&code_challenge=WtcXoh6_3hCtvn15TW0VEFrjru2kRZO8kWcFurXMo4Y
&code_challenge_method=S256
&state=QIA33u4JKTwCmXjPcZXoHyJD5h5szwFs
cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg

Things to notice:

  • The request looks a lot like the one we observed during the interactive phase, including all the PKCE machinery.
  • One important difference is the prompt=none directive. We want this to happen without any UX.
  • Another fundamental difference is that our request is accompanied by the session cookie, which should prove the user’s sign in status without prompts
  • In Auth0 we like web_message as response_mode when communicating with iframes. For the purpose of discussing differences between implicit vs authorization code grants it doesn’t make much of a difference.
    The response returns the requested code, delivered via JS as expected. The AS also takes the opportunity to update the session cookie.
1
2
3
4
200 HTTP/2.0
set-cookie: auth0=s%3AMaLRoS9Q0WdZ2oI5T4sgOxBna16OarTI.pSedvnX9ctZOkcs9lPkXcNfQn3ciUSxc%2B2K8N6XzOPg; Path=/; Expires=Sun, 06 Jan 2019 20:12:07 GMT; HttpOnly; Secure

<!DOCTYPE html><html><head><title>Authorization Response</title></head><body><script type="text/javascript">(function(window, document) {var targetOrigin = "http://localhost:3000";var webMessageRequest = {};var authorizationResponse = {type: "authorization_response",response: {"code":"a_Yja9QKgxtwCnA2","state":"QIA33u4JKTwCmXjPcZXoHyJD5h5szwFs"}};var mainWin = (window.opener) ? window.opener : window.parent;if (webMessageRequest["web_message_uri"] && webMessageRequest["web_message_target"]) {window.addEventListener("message", function(evt) {if (evt.origin != targetOrigin)return;switch (evt.data.type) {case "relay_response":var messageTargetWindow = evt.source.frames[webMessageRequest["web_message_target"]];if (messageTargetWindow) {messageTargetWindow.postMessage(authorizationResponse, webMessageRequest["web_message_uri"]);window.close();}break;}});mainWin.postMessage({type: "relay_request"}, targetOrigin);} else {mainWin.postMessage(authorizationResponse, targetOrigin);}})(this, this.document);</script></body></html>

From this point onward, the code redemption against the token endpoint plays out just like we described for the interactive portion of the flow.
Hopefully this clarified how you can perform background token renewals without requiring a refresh token in JavaScript. That works pretty well, though there’s something that kind of ruins the party for me. The mechanism described here still relies on the iframe to be able to access the session cookie. There are various situations where that access might not be granted, as it is the case in Apple’s ITP2 (see discussion here). Using a refresh token would make the issue moot, which is why I believe it is worth it to keep an eye on this space and push for more widespread adoption of the security measures that would make refresh tokens usable from JavaScript. There is an entirely different chapter about what the implications for distributed session termination would be, but this post is long enough as it is already — we’ll talk about it in some other installment.

“Heard about the new guidelines for SPAs in the latest OAuth2 security BCP? Learn how to apply all that on your Auth0 apps in this article from @vibronet.”
Tweet This

Different SPA Topologies and Alternative Approaches

Throughout the best practice doc, and the post, a key underlying assumption is that the SPA we are considering is in its most canonical form: a set of static HTML and JS files, running all logic in the browser.

In fact, it is very common for SPAs to have a server-side component. Being able to run logic on the server creates the opportunity for more approaches to the problem of securing API calls from JavaScript code running in a browser. Those approaches (and others) will be discussed by the OAuth2 working group in a browser-based apps best current practices document (BCP), currently at its third draft and under active discussion.

Just to give you a taste, let’s mention the two most common topologies.

SPAs calling APIs exclusively on its own domain

图片

Figure 3: If your APIs live on the same domain as your SPA bits, you can secure calls via session cookies just like you’d do for traditional web apps

If your SPA pages are coming down from the same domain where you host the API endpoints you want to call, you can happily dispense with all this javascript-requests-uses-and-manage-tokens brouhaha, and simply secure API calls just like you would secure postbacks to traditional web apps: via session cookies. Secure your API just like you would secure a web app for sign in, for example by slapping an OpenID Connect middleware in front of it, and let it issue and validate cookies for you. Your JS doesn’t have to contain any explicit logic for securing calls, as the browser will automatically attach the cookies for your app domain (hence the session cookie) to every request, including AJAX calls to API endpoints.

This is my favorite solution: simple and proven. The main challenge for Identity as a Service solutions is how to ask to all developers (including non-experts) whether their app is indeed eligible for this approach, and how to deal with app evolutions changing the situation (e.g. starting to cross -domain APIs too). But that’s not a problem for you, the advanced developer braving complicated blog posts like the one you are reading at the moment!

There are at least a couple of extra things that I believe will benefit from extra guidance from the upcoming browser-based BCP:

  • If the API are secured with the same middleware used to enforce web sign on, they are likely to return a 302 when the session cookie expires. 302s aren’t really actionable when returned in AJAX calls, and that means that the JS code will need some error management logic to handle the situation- one that possibly doesn’t end up sending the browser to pages controlled by an attacker
  • SPA apps often require their JS code to know some user information, say the username to be displayed on the top right corner of their UX. It would be easy to dismiss this as exercise for the reader, giving vague indications like “just expose the info you need in one of your API”. That sounds like something that should be properly threat modeled and formalized, so that it can be enshrined in SDKs rather than forcing every developer pursuing this approach to reinvent the wheel over and over again.
    Apart from the two details above, which will require you to roll your own solution, this approach is available to you TODAY if your app hosts pages and API on the same domain. Also note: just like the next one, this approach has the added bonus of sidestepping any ITP2 related issues.

SPAs using their backend to obtain and forward tokens back to the JS layer

图片

Figure 4: If you have a backend, you can use it to proxy access token requests to the AS… but you become responsible for a critical architectural component, without any protocol guidance to rely on.

Another possible use of a backend to solve this scenario is to delegate all token acquisition responsibilities to the server side portion of the code. That would sidestep all the issues discussed so far in using JavaScript to run the token acquisition logic. That sounds awesome in theory, but there are a couple of pitfalls one should consider before choosing this approach.

  • Your backend would need to pass the tokens it obtains back to the JS code, given that it’s the layer that needs to perform the API calls. The backend would also be responsible to renew those tokens when they expire. See where I am going? Your backend would turn into an authorization server of sort for your JS code, but one that isn’t described in any specification or public threat model. You’d be responsible for a critical component of your architecture, without the benefit of following a design that has been validated by the community, or without the chance to using off the shelf components implementing the feature. This is another aspect that I believe would benefit from more prescriptive guidance.
  • Tokens obtained by the backend would be marked as issued to a confidential client, but the actual client’s security characteristics would be the one of the JS layer, perhaps the most vulnerable of all public clients. Whatever API you are calling using this mechanism should NOT use the client type as a decision factor for authorization. Alternatively, the AS should provide confidential clients with a mechanism to signal that the requested token will not be used by them, so that the token bits can reflect it accordingly. And of course, sender constrained mechanisms would need to be updated to make this request by proxy possible. More food for thought for the working group.
    Just like the other alternative approach, this solution can be implemented TODAY if you do have a backend and you are willing to roll your own solution to the two points above.

I do have to stress the need for caution here. The principle “the client requesting the access token is the same client that will use the token to access the resource” is pretty fundamental, and violations might easily lead to unintended consequences. For example, imagine that your AS has logic including sensitive claims only when issuing tokens to confidential client, on the assumption that they will not be directly accessible on the end user’s device. The proxy pattern invalidates that assumption, potentially with dire consequences. If you do decide to adopt this pattern, make sure to double and triple check your solution for similar side effects.

Next steps

Did you read all the way here? You are nothing short of a hero! Hopefully your investment paid off and you are now better equipped to make decisions about how to secure your SPA.

To put a bow on all this, here’s a summary of the recommendations.

Short term

  • Track down your existing SPAs. Consider whether you are happy with your existing mitigations and want to stick with what you have (likely implicit) for the time being, or if the advantages of the new approach are worth the cost of an update. (Remember the headache pill analogy earlier.).
  • If you are about to develop a new SPA, or decide to update an existing one, consider if your provider supports new approach and to what extent. Things to consider:
    • Do they support CORS on the token endpoint? (Auth0 does)
    • Do they support sender constrained tokens tech? (Almost no one does AFAIK.)
    • Do they support refresh token rotation? (Very few do.)
    • Do they offer an SDK implementing authorization code grant in JS? (Auth0 doesn’t yet, but we will… in the meanwhile, we have a sample.)
  • If your SPA has a backend, consider whether the alternative approaches described here are right for you

Medium term

  • Keep an eye on updates here.
  • Watch for Auth0 announcements about a new SDK release supporting the approach.
    Happy coding!

About Auth0

The Auth0 Identity Platform, a product unit within Okta, takes a modern approach to identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.

Postman 入门3 - Newman

Newman

官方帮助文档地址

Newman 安装

嗯,它需要安装,因为它不是音乐播放器!Newman是为Postman而生,专门用来运行Postman编写好的脚本。Newman安装步骤:

1 需要安装Node.js,并且Node.js版本需要>=V6

2 打开控制台,运行:

1
$ npm install -g newman   // 安装newman
1
$ npm install -g  newman-reporter-html //为了能生成html的测试报告,安装html report报告模块

3 校验是否安装成功,运行:newman –version 图片

Newman 执行脚本

Newman在3版本后做了比较大的改动,但是运行命令越来越简单,这里以v4.5版本为例:

1
newman run <collection-file-source> [options]

run 后面跟上要执行的json文件或者URL(json 和 URL 都由postman导出生成),再后面跟一些参数,例如环境变量,测试报告,接口请求超时时间等等。最后给两个完整的例子做参考:

例子1,通过newman 运行postman导出的test1.json文件,并生成多种测试报告(json,junit的xml,html):

1
newman run C:\postman\test1.json --reporters cli,html,json,junit --reporter-json-export C:\postman\jsonOut.json --reporter-junit-export C:\postman\xmlOut.xml --reporter-html-export C:\postman\htmlOut.html

例子2,运行https://www.getpostman.com/collections/10d6f9f4b681917bf258(postman生成的 )中的所有api,并使用env.json作为环境变量和globals.json作为全局变量,并使用外部user.json作为外部数据,最后设置了接口请求超时时间为5S 。

1
newman run https://www.getpostman.com/collections/10d6f9f4b681917bf258 -e C:\postman\env.json --
1
iteration-data C:\postman\user.json -g C:\postman\globals.json --timeout-request 5000 --reporters cli,html --reporter-html-export C:\postman\htmlOut.html

Jenkins 结合

平时做接口自动化,避免不了最后通过Jenkins做构建。既然Newman提供了控制台命令执行方式,那么像通过Jenkins来构建也就容易多了。

步骤一:在Jenkins 机器上安装Node.js、Newman

步骤二:搭建Jenkins环境,并新建个自由风格的Job

步骤三:构建选择Execute Windows batch command,并输入newman 运行命令

图片

步骤四:因为上面命令中构建会生成junit的xml报告,所以可以在构建后用Publish JUnit test result report 插件来生成测试报告。

图片

Postman 入门3 - Newman

上次Postman 入门1 我们介绍全局变量和环境变量时已经使用过Tests 和 pre-request script 模块,但未细讲。这里我们就针对这个模块讲解一些常用的操作。

Script

Postman 提供了便捷的可视化界面操作,也提供了强大的脚本支持。Postman运行过程是基于强大的 Node.js,可以在请求发起前(Pre-request Script)和拿到请求返回后(Tests),通过编写JavaScript来实现请求间数据传递,请求参数动态生成等等。

Postman 名词定义

Pre-request Script 和 Tests 可以在集合(Collections)、夹(Folder)、请求(Request)中设置。估计大家可能会有个疑问,集合、夹、请求都只是指啥,这里补充一张图了解下Postman定义的名词,也算对上次分享的一个补充。

图片

脚本执行顺序

在Postman中,单个请求的脚本执行顺序如下:

  • 请求里的pre-request script 将在 Request 发起之前执行
  • 请求里的Tests 将在Response 获取之后执行
    图片

对于一个集合里的请求,将按一下顺序执行

集合pre → 夹pre → 请求pre → Request → Response → 集合Tests → 夹Tests → 请求Tests

图片

注意:集合夹中的Pre-quest script 和 Tests script 是每个Request请求之前和Response之后都会执行一次

知道执行顺序和执行次数,平时在使用这模块时根据具体场景合理应用便可。

了解Scprit大体作用后,我们来看几个常见场景,以及常见脚本编写。

Tests

场景1:API间参数传递

集合下所有API 请求发起都需要一个token或uid一类的字段,而这个字段可以由登录后从response获取。那么如果我们不充分利用好Tests,那么我们测试调用时往往需要先执行下 Login 然后拷贝 uid,接着修改要发起请求的uid字段,再发起请求,这样一个集合下如果有几十个API ,那么我们可能需要黏贴修改 N个API的uid字段,这非常麻烦。

正确做法,在login API的Tests 中编写个脚本以达到,Login请求拿到Response后往环境变量和全局变量写入一个变量供其他API使用。

例如Login API 返回大体如下的Json

1
2
3
4
5
6
7
8
{
"code": 1,
"content": {
"userId": "123123"
},
"msg": "成功",
"uid": "7a57ea7b-19cd-1234-1234-fa1be9ff045d1557562660436",
}

为了获得uid的值,并写入环境变量,我们可以在Tests 中加入如下脚本

1
2
3
4
5
6
// 把reponse 的body转成Json
let jsonData = JSON.parse(responseBody);
// 解析获得Json中的 uid
let uid = jsonData.uid;
// 设置一个环境变量 uid 值为 json解析获取到的值
pm.environment.set("uid", uid);

这时发起这个登录请求后,我们就会看到对应的环境变量中增加了一个 uid 环境变量,那么同集合里其他需要uid的API只需要在需要的位置添加 便可以获取到最新的uid值

图片

场景2:Response校验

通过Postman 测试API时,除了肉眼去看结果返回值,我们还可以在Tests中编写一些脚本用于校验我们的response。

例如还是Login接口,Login response和场景1是一样的,我们想校验接口返回的数据、状态和请求时间等。我们可以在Tests编写如下脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pm.test("Response time is less than 200ms", function () {
// 获取请求时间,并校验小于200ms
pm.expect(pm.response.responseTime).to.be.below(200);
});
pm.test("Status code is 200", function () {
// 获取请求状态码,并校验是否为 200
pm.response.to.have.status(200);
});

pm.test("json test", function () {
// 获取response json
var jsonData = pm.response.json();
// 校验msg的值为 成功
pm.expect(jsonData.msg).to.eql("成功");
// 校验 status 为 1
pm.expect(jsonData.content.status).to.eql(1);
});

我们看看更多Response的属性,当然这些Response熟悉不适用与下面的pre-request script:

  • **responseHeaders[String]  {Object}:获取response的头部信息,添加String参数则获取指定的头部信息。但官方不推荐这么使用,更推荐使用postman.getResponseHeader(‘xxx’)**。
    1
    2
    3
    console.log(responseHeaders)
    console.log(responseHeaders['Content-Type'])
    console.log(postman.getResponseHeader('Content-Type'))
  • responseCookies {Array}:返回一个数组,可以通过postman.getResponseCookie(cookieName)** 获取具体的cookie信息。
  • **responseBody {string}**:返回响应正文文本的字符串。可以用JSON.parse()转成数组。
  • **responseTime {number}**:返回响应时间,单位ms
  • responseCode[String] {Object}:返回的状态码,不带参数会返回4个属性。 常用的属性是 code 就是状态码。
    1
    2
    console.log(responseCode)
    console.log(responseCode['code'])

更多常用Tests脚本解释

  • 设置环境变量

    1
    pm.environment.set("variable_key", "variable_value");
  • 获取环境变量

    1
    pm.environment.get("variable_key");
  • 清除环境变量

    1
    pm.environment.unset("variable_key");
  • 设置全局变量

    1
    pm.globals.set("variable_key", "variable_value");
  • 获取全局变量

    1
    pm.globals.get("variable_key");
  • 清除全局变量

    1
    pm.globals.unset("variable_key");
  • 获取一个变量:这个将从环境变量和全局变量中搜索变量,优先从环境变量中搜索

    1
    pm.variables.get("variable_key");
  • 发起一个请求

    1
    2
    3
    pm.sendRequest("https://postman-echo.com/get", function (err, response) {
    console.log(response.json());
    });
  • 检查response body是否包含某个字符串

    1
    2
    3
    pm.test("Body matches string", function () {
    pm.expect(pm.response.text()).to.include("string_you_want_to_search");
    });
  • 检查response body是否等于某个字符串

    1
    2
    3
    pm.test("Body is correct", function () {
    pm.response.to.have.body("response_body_string");
    });
  • 检查JSON值

    1
    2
    3
    4
    pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
    });
  • 校验某结果类型是否存在:如下是说response header包含Content-Type

    1
    2
    3
    pm.test("Content-Type is present", function () {
    pm.response.to.have.header("Content-Type");
    });
  • 响应时间小于200毫秒

    1
    2
    3
    pm.test("Response time is less than 200ms", function () {
    pm.expect(pm.response.responseTime).to.be.below(200);
    });
  • 状态代码是200

    1
    2
    3
    pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
    });
  • 校验某对象是否包含某个字符串:例如下面校验response的body 包含 msg 属性

    1
    2
    3
    pm.test("Body is correct", function () {
    pm.response.to.have.body("msg");
    });
  • 校验结果在某范围中:如下校验请求状态码是201,202

    1
    2
    3
    pm.test("Successful POST request", function () {
    pm.expect(pm.response.code).to.be.oneOf([201,202]);
    });
  • 将XML主体转换为JSON对象:

    1
    var jsonObject = xml2Json(responseBody);

更多官方demo


pre-request script

场景3:自定义变量

Postman尽管提供了不少的变量,但是有时这些变量均无法满足我们的需求。 例如创建用户API,我们需要一个11位并且1开头的不重复的电话号码。例如某API有个时间参数,我们需要传入当前日期等等。 那么这些我们就可以通过pre-request script 中通过编写简单的JS,使可支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 生成10为随机数
function ronNum(){
let num = "";
for(let i=0;i<10;i++){
num +=Math.floor(Math.random()*10)
}
console.log(num)
return num;
}

// 生成1开头的11位随机数
let phoneNo ="1"+ronNum();
// 设置成环境变量供API引用
pm.environment.set("phone", phoneNo);

// 生成 xxxx-xx-xx格式日期
let date = new Date().getFullYear()+"-"+((new Date()).getMonth()+1)+"-"+(new Date()).getDate();
// 设置成date 环境变量
pm.environment.set("date", date);

场景4:接口依赖

例如调用删除API,你们为了避免我们每次删除都有效,这时我们往往在删除之前先调用一下添加的接口,这时我们可以再pre-request script中编写个调用add的API,先生成一条数据。在例如假设我们调用API前我们需要需要查阅数据库(假设我们已经提供了数据库交互的API),那么我们也可以把调用数据库API的步奏放于pre-request script。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 调用 请求
pm.sendRequest("https://postman-echo.com/get", function (err, response) {
// 输出 reponse
console.log(response.json());
});



// 登陆 请求
pm.sendRequest({
url: "http://admin{{env}}.com/luckyadminweb/checkLogin.do_",
method: "POST",
header: {
"Content-Length": "1",
"Content-Type": "application/x-www-form-urlencoded"
},
body: {
mode: 'urlencoded',
urlencoded: [
{key: "username", value: "user1"},
{key: "password", value: "password1"}
]
},
function (err, res) {
console.log(res);
}
});

更多常用pre-request script脚本解释

跟Tests脚本类似,除了Response相关的不支持外,基本都支持。

Postman 内置支持库

Postman 现在不仅仅支持原生是JavaScript ,Postman 提供的 沙箱(Sandbox)里面还包含了如下库:

  • Lodash:JS实用程序库
  • cheerio:jQuery子集,提供更快速,精简的API(4.6.0及更高版本中提供)
  • BackboneJS 不推荐使用:提供简单的模型,视图和集合。这将在未来版本的沙箱中删除。
  • SugarJS 推荐使用:使用有用的方法扩展本机JS对象。这将在未来版本的沙箱中删除。
  • tv4 JSON模式验证器:针对json-schema草案的v4,用于验证JSON对象
  • CryptoJS:标准和安全的加密算法。支持的算法:AES,DES,EvpKDF,HMAC-MD5,HMAC-SHA1 / 3/256/512,MD5,PBKDF2,Rabbit,SHA1 / 3/224/256/512,TripleDES
    以上库不需要导出便可直接使用,Postman 桌面版还提供了 require

**require(moduleName:String):function → ***

通过该requie还可以使用如下面模块。

  1. ajv → v6.6.2
  2. atob → v2.1.2
  3. btoa → v1.2.1
  4. chai → v4.2.0
  5. cheerio → v0.22.0
  6. crypto-js → v3.1.9-1
  7. csv-parse/lib/sync → v1.2.4
  8. lodash → v4.17.11 (when used with require, the inbuilt _ object is for v3.10.1)
  9. moment → v2.22.2 (sans locales)
  10. postman-collection → v3.4.0
  11. tv4 → v1.3.0
  12. uuid → (the module loaded is a shim for original module)
  13. xml2js → v0.4.19
    例如 chai 模块为例看个demo:
1
2
3
4
5
6
7
8
// 导入 chai 模块
const chai = require('chai')
const assert = chai.assert;

pm.test("chai test", function () {
// 校验
assert.equal("abc", "abc");
});

Runner

Postman 带有Runner功能,用于批量运行脚本,但是在运行过程中如果集合中某个API调用失败,则剩下API的也将停止运行。

该功能主要有两种用途,一种是逐个运行某个集合下的多个API;另一种是针对某个或某场景API采用数据驱动的方式运行,这个常用于批量造数据。

界面介绍

入口

常用两个入口一个是直接Heade bar中的Runner按钮,一个是对应集合或者夹中的Run 按钮。

图片

图片

界面介绍

图片

Collection or folder

通过这个选择要运行的集合或者文件夹。运行顺序从上到下。

Environment

选择运行时使用的环境变量

Iterations

迭代器用于设置集合运行的次数

Delay

设置运行中每个请求之间的时间间隔,单位毫秒

Log responses

设置集合运行时的响应日志记录。

  • For all requests:所有请求的reponse都记录
  • For failed requests:只显示失败的reponse记录
  • For no requests:都不显示reponse记录

Data

为运行的集合API提供外部数据,支持text、csv和json的格式文件。

Persist variables

默认集合运行中任何环境变量的更改都不会反映到外部的请求构建器中。勾选起来则会替换外部构建器中的环境变量。

批量运行

  1. 选取一个集合

  2. 选择环境变量

  3. 设置每个请求间隔时间(可不设置)

  4. 点击 Run
    图片

  5. 运行界面可以查看每个请求的信息(如果Log Response设置为For no requests)则看不到任何的请求返回图片

数据驱动方式运行

数据驱动方式除了用于批量测试等价类的测试用例,更常用于通过接口批量造数据。例如我需要10个订单,那么我可以创建个下订单的集合,并在集合从上而下编写下订单过程需要调用的API,并做好接口间的数据传递。

下面我们就以批量测试等价类用例为例子讲解,批量造数据也是类似做法无非过程时常需要调用多个接口罢了。

假设有如下一个登录接口,我需要用不同的账号密码测试登录。

图片

  1. 那么第一步我们可以本地创建一个Json或者csv文件,设定username和password两个字段,并设置要运行的测试值:
  • csv
    图片

  • json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [
    {
    "userName": "u1",
    "password": "p1"
    },
    {
    "userName": "u2",
    "password": "p2"
    },
    {
    "userName": "u3",
    "password": "p3"
    }
    ]
  1. 设置请求参数,通过获取文件中的参数值
    图片

  2. Runer运行,设定Data,选择你的csv或json文件后会自动帮你统计出 Iterrations,并可以通过Preview 预览数据源
    图片

  3. 点击run,可以结果页面看到我们一个api发起了3次请求,并每次请求都从数据源拿去一条对应的数据。图片

Postman 入门1- 安装、变量、代理

简介

https://www.yuque.com/miyang.meyoung/devops/mwikkg

Postman 是一款可以方便我们调试、测试API的工具,甚至我们可以通过Postman 与 Newman结合,还可以批量运行API达到API自动化测试的目的。下面都是以当前最新的V7.0.9为例演示。

简单分析:

优点

  1. 上手容易,简单易用
  2. 提供代理,提高API配置是速度
  3. 官方文档齐全,用户众多
  4. 提供多种参数化方式
  5. 等等

不足

  1. 非开源,可扩展性差,部分功能收费
  2. 无法与数据库结合(后期我们通过第三方的方式解决这个问题)
  3. Cases验证方式比较不直观,得通过编写js方式完成
  4. 等等

简单使用:

官方地址:https://www.getpostman.com/

Postman 安装

Postman 主要有两个版本,一种早期版本是Chrome浏览器的一个应用,所以通过Chrome应用商店可以直接找到,并点击安装,如下图

图片

另一种是现在主推的桌面版,根据自己的系统找到对应的版本下载,跟普通软件一样安装便可。

整体界面介绍

图片

Postman 提供了多窗口,多标签的界面。扣除菜单外,大概可以分为4个区域,Header Bar 、Side Bar 、Buider 、Footer Bar。

Header Bar

图片

  • New按钮:创建请求,集合,环境,文档,模拟服务器和监视器。
  • Import按钮 - 支持API集合,环境,数据,WADL,Swagger, RAML, Open API 3.0或cURL等导入Postman。
  • Runner按钮 - 打开批量运行模块
  • Open New图标 - 打开一个新的“tab”,“Postman window ”或“Runner Window”。
  • Workspace菜单 - 打开工作区菜单,您可以在其中查看个人和团队工作区,以及创建和管理工作区。
  • SYNC图标 - 更新Postman帐户的状态。
  • 代理\拦截器图标 - 管理代理或拦截器设置。
  • 设置图标 - 管理邮递员应用程序设置并查找其他支持资源。
  • 通知图标 - 接收通知或广播。
  • 图标 - Tweet about us.
  • 用户下拉列表 - 显示当前用户并提供以下选项:“配置文件”,“帐户设置”,“通知首选项”,“活动会话”和“添加新帐户”。
  • upgrade菜单 - Team 模式还剩余的资源,Team设置等

Postman 侧边栏Collections主要用于管理请求和集合。History 主要用于查看历史记录。

Builder

Postman核心区域,每个请求的相关信息均这边配置。后面细讲。

图片

  • ShowHide按钮 - 显示或隐藏侧边栏
  • Find按钮 - 全局搜索
  • Postman Console按钮 - 打开Postman的控制台,可以非常便捷查看到每个请求发起后的完整信息
  • Bootcamp按钮 - 训练营可以了解成帮助,可以一步一步教你多种类型的操作
  • Build/Browse选项 - 默认Build,切换到Browse后可以切换到浏览器的方式查看一些信息
  • Two Pane view按钮 - 用于切换Builder的显示方式
  • Keyboard Shortcuts按钮 - 查看快捷键,打开也是设置界面
  • Help&Feedback按钮 - 帮助和反馈相关

Settings

Postman尽量减少必须更改的设置数量,因此我们建立了一些默认设置。但是,考虑到用例的多样性,如果您需要进行调整,那么如下:

图片

  • 在请求中装饰键和值(Trim keys and values in request body): 如果使用表单数据(form-data )或网址编码(url-encoded)模式将数据发送到服务器,将其切换为ON将导致任何参数被装饰。
  • SSL证书验证(SSL certificate verification)(仅限本机应用程序):防止应用程序在发出请求时检查SSL证书的有效性。详细了解管理客户端证书。
  • 语言检测(Language detection): 将此设置为JSON将强制JSON呈现,而不管Content-Type标题的响应。
  • XHR超时(ms):设置应用程序在等待服务器响应之前等待响应的时间。值为0表示无穷大 - Postman将永远等待回复。
  • 编辑器字体大小(Editor Font Size):调整Postman中出现的文本的字体大小(以像素为单位)。
  • 双窗格视图(Two-pane view):在下面或旁边显示请求之间切换。
  • 发送无缓存头(Send no-cache header)(推荐):发送无缓存头,确保从服务器获得最新的响应。
  • 发送Postman令牌header(Send Postman Token header): 这主要用于绕过Chrome中的错误。如果XmlHttpRequest处于待处理状态,并且使用相同的参数发送另一个请求,则Chrome会为其返回相同的响应。发送随机令牌避免了这个问题。这也可以帮助您区分服务器端的请求。
  • 单击链接时保留headers(Retain headers when clicking on links): 如果您单击响应中的链接,Postman会使用该URL创建一个新的GET请求。如果要保留在先前的请求中设置的头文件可以开启“ON”。如果您主要访问受保护的资源,这将非常有用。
  • 自动遵循重定向(Automatically follow redirects): 防止返回300系列响应的请求被自动重定向。
  • 向Postman发送匿名使用数据(Send anonymous usage data to Postman): 选项可禁用向Postman发送基本的匿名使用数据(按钮点击和应用程序事件)。我们使用有用的数据使Postman成为更好的产品。

测试GET类型API

以豆瓣搜索图书API为例

图片

这个接口的请求方式为get,postman默认为就为get,所以在url栏输入被测接口地址

图片

在接口文档中,q与tag其中的一个是必填项,所以这里需要配置参数,点击url右侧的params,并输入参数

图片

点击send按钮发送请求,在下面可以看到返回的response,响应的状态码,与响应时间

图片

response还可以以不同的方式查看,并且还可以看到cookies,headers信息

图片

测试POST类型API

以Postman自带的接口为例

选取请求的方法为POST,并输入接口地址,因为post请求大多是json形式,所以可以打开Body选项,并选择JSON(application/json)形式,输入请求的json,同样其余的操作跟GET接口一样。

图片

除了请求格式为Json外,还有别的请求类型,如我们应用大多常见的(application/x-www-form-urlencoded),如下我们Admin后台的登录,请求类型为x-www-form-urlencoded,那只需要选择x-www-form-urlencoded,并配上相应的参数和值便可。

图片

变量的设置

编写的API往往需要在多个环境下执行,而Postman 提供了两种类型的变量:环境变量和全局变量,从而很好的解决了这个问题。同时变量还常用于关联接口间的参数传递。

环境变量有效范围仅仅在于当前集合可用,全局变量对所有的集合都可用。

环境变量

api可能需要在不同的环境中运行,所以api请求的服务器地址不能写死,希望是可以配置的,创建环境变量有多种方式。

  1. 手工预先创建环境变量
    图片

  2. 代码自动创建环境变量自动新建环境变量可在两种情况下创建,但是创建方式相同都是用了postman提供的方法:

    1
    pm.environment.set("variable_key", "variable_value");
1
2
2.1 在某个请求发起之前创建:
在Pre-request Script标签里面添加代码:

图片

2.2 在某个请求发起之后创建:

在Tests标签里面添加如下

图片

全局变量

全部变量跟环境变量的创建类似,也可以通过手工预先创建或者通过代码去创建。

  1. 通过手工预先创建
    图片

  2. 通过代码创建2.1 在请求发起前创建在Pre-request Script标签里面添加代码:

    1
    pm.globals.set("variable_key", "variable_value");

     2.2 在请求发起后创建

在Tests标签里面添加如下:

1
pm.globals.set("variable_key", "variable_value");

INITIAL VALUE 和 CURRENT VALUE

在默认情况下,设置环境变量和全局变量一个key(VARIABLE)对应两个value(INITIAL VALUE 和 CURRENT VALUE),那这两个有啥区别?

图片

  • INITIAL VALUE :初始值本地脚本运行时不会获取该值,该值主要用处Team模式或者导出变量时设定的默认值
  • CURRENT VALUE :当前值则是实实在在本地脚本运行时使用的变量值,同时通过脚本设置变量时默认也是设置的是这个值。

变量的引用

在需要的地方加上****便可

随机数

PostMan 除了提供环境变量和全局变量外,还提供了三种随机数。

:添加一个V4风格GUID

:将当前的时间戳,精确到秒

:添加0和1000之间的随机整数

更多的变量

往往内置的变量无法满足我们的测试需求,这时我们还可以通过编写简单的脚本,实现更多定制化的变量配置。这个模块后会专门找个时间讲解演示。


代理\拦截

Postman 提供了两种类型代理。 一种是下图中的Client角色,但Postman设置了代理,这样Postman发起的请求都会先经过Proxy,再到Server 。 还一种是下图的 Proxy角色,Postman拦截client发过来的请求。

图片

拦截

下面以拦截为例子,介绍Postman如果设置成一个代理器,拦截请求从而到达能快速完成一个API配置的目的。但是目前不支持HTTPS协议的抓取。当然如果你系统就是https协议的,你可以用旧版chrome应用版,它是支持的。再或者你可以用fiddler抓包,然后当个导出cURL script,再导入到postman也可以达到快速配置的目的。

  1. 点击proxy,打开Proxy设置,并设置代理端口和抓取的保存位置,截图设置了5555端口,并保存到History.图片

Filters 用于过滤,URL Contains 用于设置要抓取的URL,Url Does not contain 用于过滤不想抓取到的信息,Methods 用于设置抓取的请求方式。

图片

  1. 设置浏览器或者系统代理,以win 10系统为例子,打开系统 设置代理。图片

  2. 打开浏览器,正常请求,会发现这时postman 的history也会捕获到请求。图片

Postman 既然可以作为代理,同样的也可以抓取移动端IOS、Andorid 的请求,这块的设置这里就不说明,跟fiddler类似,同一个局域网内设置wifi代理。